/*
 * Copyright 2011 Clean GWT
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
 * in compliance with the License. You may obtain a copy of the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software distributed under the License
 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 * or implied. See the License for the specific language governing permissions and limitations under
 * the License.
 */
package com.cleangwt.user.client.ui;

import java.util.ArrayList;
import java.util.List;

import com.cleangwt.uibinder.client.TemplateSupport;
import com.cleangwt.user.shared.Option;
import com.cleangwt.user.shared.Options;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.NodeList;
import com.google.gwt.dom.client.OptionElement;
import com.google.gwt.dom.client.SelectElement;
import com.google.gwt.event.dom.client.ChangeEvent;
import com.google.gwt.event.dom.client.ChangeHandler;
import com.google.gwt.event.logical.shared.ValueChangeEvent;
import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.user.client.ui.HasValue;

/**
 * A {@link com.google.gwt.user.client.ui.ListBox} supports element wrapping.
 * 
 * The list box provides a string value that may be a value of single selection or the value that
 * composed from multi selection, the composed value may like 'value1, value2, value3...', each
 * value is just packed up with the {@link Options#FORM_DELIMITER}. For correct representation, you
 * had better not add any option value that contains the {@link Options#FORM_DELIMITER}.
 * 
 * @author Jess
 * @version 1.0
 * @since 1.0
 */
public class ListBox extends com.google.gwt.user.client.ui.ListBox implements HasValue<String>,
    RequiresOptions {

  /**
   * Creates a list box that wraps an existing &lt;select&gt; element of template.
   * 
   * @param template the template
   * @param element the element to be wrapped
   */
  public static ListBox wrap(TemplateSupport template, Element element) {
    ListBox widget = new ListBox(element);
    template.wrap(widget);
    return widget;
  }

  private String placeholder;
  private String lastValue;
  private boolean valueChangeHandlerInitialized;

  /**
   * Creates a empty list box.
   */
  public ListBox() {
    this(false);
  }

  /**
   * Creates a list box with the specified placeholder.
   * 
   * @param placeholder the placeholder
   */
  public ListBox(String placeholder) {
    this(false);
    setPlaceholder(placeholder);
  }

  /**
   * Creates an empty list box with the given 'isMultipleSelect'.
   * 
   * @param isMultipleSelect specifies if multiple selection is enabled
   */
  public ListBox(boolean isMultipleSelect) {
    super(isMultipleSelect);
  }

  /**
   * This constructor may be used by subclasses to explicitly use an existing element. This element
   * must be a &lt;select&gt; element.
   * 
   * @param element the element to be used
   */
  protected ListBox(Element element) {
    super(element);
  }

  /**
   * Adds an option.
   * 
   * @param option the option
   */
  public void addOption(Option option) {
    addItem(option.getName(), option.getValue() == null ? "" : option.getValue());
    if (option.getDisabled() != null && option.getDisabled()) {
      Element e = this.getElement().getChild(this.getItemCount() - 1).cast();
      e.setAttribute("disabled", "");
    }
  }

  @Override
  public HandlerRegistration addValueChangeHandler(ValueChangeHandler<String> handler) {
    if (!valueChangeHandlerInitialized) {
      valueChangeHandlerInitialized = true;
      addChangeHandler(new ChangeHandler() {

        @Override
        public void onChange(ChangeEvent event) {
          ValueChangeEvent.fire(ListBox.this, getValue());
        }
      });
    }
    return this.addHandler(handler, ValueChangeEvent.getType());
  }

  @Override
  public void clear() {
    super.clear();
    if (placeholder != null) {
      addItem(placeholder, "");
    }
  }

  /**
   * Retrieves the text of selection.
   * 
   * @return the text
   */
  public String getText() {
    return getItemText(getSelectedIndex());
  }

  @Override
  public String getValue() {
    if (isMultipleSelect()) {
      SelectElement e = this.getElement().cast();
      List<String> selections = new ArrayList<String>();
      for (int i = 0; i < getItemCount(); i++) {
        OptionElement option = e.getOptions().getItem(i);
        if (option.isSelected()) {
          selections.add(option.getValue());
        }
      }
      return Options.form(selections);
    } else {
      if (getSelectedIndex() == -1) {
        return null;
      } else {
        return getValue(getSelectedIndex());
      }
    }
  }

  /**
   * Sets the options.
   * 
   * @param options the options
   */
  @Override
  public void setOptions(List<Option> options) {
    setOptions(options, false);
  }

  /**
   * Sets the options and keeps the selected option when keepSelection is true.
   * 
   * @param options the options
   * @param reset whether to reset the last value(the value may be set before options are added to
   *          this list box)
   * @see #setValue(String,boolean)
   */
  public void setOptions(List<Option> options, boolean reset) {
    clear();
    if (options != null) {
      for (Option option : options) {
        addOption(option);
      }
    }
    if (!reset) {
      setValue(lastValue);
    }
  }

  /**
   * Sets the placeholder.
   * 
   * @param placeholder the placeholder
   */
  public void setPlaceholder(String placeholder) {
    assert placeholder != null : "Placehodler must not be null.";
    assert this.placeholder == null : "List box has been initialized, you might not set the placeholder again.";
    this.placeholder = placeholder;
    this.insertItem(placeholder, "", 0);
  }

  @Override
  public void setValue(String value) {
    setValue(value, false);
  }

  /**
   * Sets the value. If the options of this list box have not been set, keep the value. When the
   * {@link #setOptions(List, boolean)} is called, it will recheck the option of value in this list
   * box.
   * 
   * @param value the value to set
   * @param fireEvents whether to fire events
   * @see #setOptions(List, boolean)
   */
  @Override
  public void setValue(String value, boolean fireEvents) {
    this.lastValue = value;
    String oldValue = getValue();

    String[] selections = Options.unform(value);
    // reset
    if (placeholder == null) {
      setSelectedIndex(-1);
    } else {
      setSelectedIndex(0);
    }
    if (value != null) {
      SelectElement e = this.getElement().cast();
      NodeList<OptionElement> options = e.getOptions();
      for (String selection : selections) {
        for (int i = 0; i < options.getLength(); i++) {
          OptionElement option = options.getItem(i);
          if (selection.equals(option.getValue())) {
            option.setSelected(true);
            break;
          }
        }
      }
    }
    if (fireEvents) {
      ValueChangeEvent.fireIfNotEqual(this, oldValue, value);
    }
  }

}
